/**
* Copyright 2012 R King
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.kingamajick.admp.maven;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.ImageTranscoder;
import org.apache.batik.util.XMLResourceDescriptor;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.w3c.dom.Document;
import org.w3c.dom.svg.SVGSVGElement;
import com.github.kingamajick.admp.maven.beans.Density;
import com.github.kingamajick.admp.maven.transcoder.TranscoderFactory;
import com.github.kingamajick.admp.maven.transcoder.TranscoderFactoryException;
import com.github.kingamajick.admp.maven.util.Asserts;
import com.github.kingamajick.admp.maven.util.Constants;
/**
* Rasterizes any SVGs contained <code>${svgDirectory}</code> (default: <code>'src/main/svg'</code>) to
* <code>'${project.build.outputDirectory}/res'</code> for any densities specified.
* <ul>
* <li>ldpi (default)</li>
* <li>mdpi (default)</li>
* <li>hdpi (default)</li>
* <li>xhdpi (default)</li>
* <li>nodpi</li>
* <li>tvdpi</li>
* </ul>
*
* @author R King
*
* @goal rasterize
*/
public class RasterizeSVGMojo extends AbstractMojo {
private UserAgent userAgent;
private DocumentLoader loader;
private BridgeContext context;
private SAXSVGDocumentFactory svgDocFactory;
private TranscoderFactory transcoderFactory;
/**
* The directory containing the SVG resource to be rasterized.
*
* @parameter expression="${svgDirectory}" default-value = "src/main/svg"
*/
File svgDirectory;
/**
* @parameter expression="${project.build.outputDirectory}/res"
* @readonly
*/
File targetDir;
/**
* A list of {@link Density}s, if none are specified the following configuration is used:
* <ul>
* <li>name : 'drawable-ldpi', scale-factor : 0.75f</li>
* <li>name : 'drawable-mdpi', scale-factor : 1.00f</li>
* <li>name : 'drawable-hdpi', scale-factor : 1.50f</li>
* <li>name : 'drawable-xhdpi', scale-factor : 2.00f</li>
* </ul>
*
* @parameter expression="${densities}"
*/
List<Density> densities;
/**
* The type to raterized the SVGs to. The available formats are <code>png</code> and <code>jpg</code>
*
* @parameter expression="${rasterizedType}" default-value = "png"
*/
String rasterizedType;
/**
* Create a directory with the specified name, and all the necessary parent directories to do that.
*
* @param root
* @param newDirName
* @return
* @throws MojoFailureException
* if it was not possible to create the directory.
*/
static File createDirectory(final File root, final String newDirName) throws MojoFailureException {
File dir = new File(root, newDirName);
if (!dir.exists()) {
boolean success = dir.mkdirs();
if (!success) {
throw new MojoFailureException("Unable to make directory " + dir.getAbsolutePath());
}
}
return dir;
}
public RasterizeSVGMojo() {
this.userAgent = new UserAgentAdapter();
this.loader = new DocumentLoader(this.userAgent);
this.context = new BridgeContext(this.userAgent, this.loader);
this.context.setDynamicState(BridgeContext.DYNAMIC);
this.svgDocFactory = new SAXSVGDocumentFactory(XMLResourceDescriptor.getXMLParserClassName());
this.transcoderFactory = new TranscoderFactory();
}
/*
* (non-Javadoc)
*
* @see org.apache.maven.plugin.Mojo#execute()
*/
public void execute() throws MojoExecutionException, MojoFailureException {
if (this.densities.size() == 0) {
Density.defaults(this.densities);
}
ImageTranscoder transcoder = null;
try {
transcoder = this.transcoderFactory.create(this.rasterizedType);
}
catch (TranscoderFactoryException e) {
throw new MojoExecutionException("Unable to create transcoder", e);
}
Map<String, File> svgsToProcess = getSVGsToProcess(this.svgDirectory);
for (Entry<String, File> svgToProcess : svgsToProcess.entrySet()) {
try {
String inputURI = svgToProcess.getValue().toURI().toString();
Document svgDoc = this.svgDocFactory.createDocument(inputURI);
SVGSVGElement svgDocElement = (SVGSVGElement) svgDoc.getDocumentElement();
float width = svgDocElement.getWidth().getBaseVal().getValue();
for (Density density : this.densities) {
getLog().debug(
"Rasterizing " + svgToProcess.getValue() + " -> " + density.getName() + "/" + svgToProcess.getKey() + "." + this.rasterizedType + " [" + density.getScaleFactor() + "]");
File outputDir = createDirectory(this.targetDir, density.getName());
File outputFile = new File(outputDir, svgToProcess.getKey() + "." + this.rasterizedType);
outputFile.createNewFile();
OutputStream os = new FileOutputStream(outputFile);
TranscoderInput input = new TranscoderInput(svgDoc);
TranscoderOutput output = new TranscoderOutput(os);
transcoder.addTranscodingHint(ImageTranscoder.KEY_WIDTH, new Float(Math.ceil(density.getScaleFactor() * width)));
transcoder.transcode(input, output);
}
}
catch (IOException e) {
throw new MojoFailureException("Unable to rasterize " + svgToProcess.getValue().getAbsolutePath(), e);
}
catch (TranscoderException e) {
throw new MojoFailureException("Unable to rasterize " + svgToProcess.getValue().getAbsolutePath(), e);
}
}
}
/**
* Returns a map of all SVGs contain in the rootDirectory and its sub directories, the map is keyed by the output name for that file
* when rasterized and contains the input file as the value. The output names will be in lower case, and in the case where the file is
* contained in a sub directory, the output file name will be prefixed with the directory path separated by '_', i.e
* ${subDir1}_${subDir2}_${imageFileName}.
*
* @param rootDirectory
* @return
*/
Map<String, File> getSVGsToProcess(final File rootDirectory) {
Asserts.notNull(rootDirectory, "rootDirectory");
Map<String, File> fileMappings = new HashMap<String, File>();
getSVGsToProcess(fileMappings, rootDirectory.listFiles(), "");
return fileMappings;
}
/**
* Processes the file list for potential SVG files. If a file is found it is added to the map with a key of the file name prefixed with
* the file name prefix. If a potential SVG file is found to be a directory, it will call this method recursively to process the
* potential SVGs in this folder, with the fileNamePrefix appended by the directory name and a '_'
*
* @param fileMappings
* @param potentialSVGs
* @param fileNamePrefix
*/
void getSVGsToProcess(final Map<String, File> fileMappings, final File[] potentialSVGs, final String fileNamePrefix) {
for (File potentialSVG : potentialSVGs) {
String potentialSVGName = fileNamePrefix + potentialSVG.getName();
if (potentialSVG.isFile() && potentialSVG.getName().toLowerCase().endsWith(Constants.SVG_FILE_TYPE)) {
// Output file name sans the extension
String outputFileName = potentialSVGName.substring(0, potentialSVGName.length() - Constants.SVG_FILE_TYPE.length());
getLog().debug("Mapping " + potentialSVG + " to " + outputFileName);
fileMappings.put(outputFileName, potentialSVG);
}
else if (potentialSVG.isDirectory()) {
getSVGsToProcess(fileMappings, potentialSVG.listFiles(), potentialSVGName + "_");
}
else {
getLog().debug("Ignoring resource " + potentialSVG);
}
}
}
}